package org.zn.note.spring.controller;

import lombok.extern.slf4j.Slf4j;
import org.springframework.dao.EmptyResultDataAccessException;
import org.springframework.data.domain.PageRequest;
import org.springframework.http.HttpStatus;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.Errors;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.bind.support.SessionStatus;
import org.zn.note.spring.dao.jdbc.OrderRepository;
import org.zn.note.spring.dao.jpa.OrderJpaRepository;
import org.zn.note.spring.data.OrderProps;
import org.zn.note.spring.domain.Order;
import org.zn.note.spring.domain.User;

import javax.validation.Valid;

@Slf4j
@Controller
@RequestMapping("/order")
public class OrderController {

    private OrderRepository orderRepo;
    private OrderJpaRepository orderJpaRepository;
    private OrderProps orderProps;

    public OrderController(
            OrderRepository orderRepo,
            OrderJpaRepository orderJpaRepository,
            OrderProps orderProps) {
        this.orderRepo = orderRepo;
        this.orderJpaRepository = orderJpaRepository;
        this.orderProps = orderProps;
    }

    @GetMapping("/current")
    public String orderForm(Model model) {
        model.addAttribute("order", new Order());
        return "orderForm";
    }

    @PostMapping
    public String processOrder( // 要提交表单的时候，验证数据的合法性
                                @Valid Order order,  // 只在bean上加条件不行，要主动验证
                                Errors errors, // 数据校验的错误信息
                                SessionStatus sessionStatus,
                                @AuthenticationPrincipal User user // 拿到认证信息(用户信息)
    ) {
        if (errors.hasErrors())
            return "design";
        /*
        这里是提交订单，如果有用户信息，就不用再让用户填写了，所以要拿到用户信息(认证信息)
        1、注入 Principal 对象到控制器方法 中；
            方法参数(..., Principal principal)，会自动注入，然后需要根据用户名查询一下用户信息：
            User user = userRepository.findByUsername(principal. getName());
        2、注入 Authentication 对象到控制器方法中；
            方法参数(..., Authentication authentication)，会自动注入，然后就和方法4一样了
        3、使用@AuthenticationPrincipal 注解来标注方法；
            本例子使用的方法
        4、使用 SecurityContextHolder 来获取安全上下文。
            Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
            User user = (User) authentication.getPrincipal();
            优点：可以在任何地方拿到认证(用户)信息。（上面那些都需要自动注入，只有这种是可以通过编码获取的）
            缺点：要写这么多业务无关的代码，没有注解方便
        */
        order.setUser(user); // 订单中一些信息是可以预填入的，比如用户的电话、住址等，所以要拿到用户信息
        // 保存订单后，就要重置session（清除session里的order，不然下次taco时，又加到这个旧order里了）
        sessionStatus.setComplete();
        log.info("Order submitted: {}", order);
        return "redirect:/";
    }

    // 列出当前用户的订单
    @GetMapping
    public String orderForUser(
            @AuthenticationPrincipal User user, Model model) {
        model.addAttribute(
                "orders",
                orderRepo.findByUserOrderByPlacedAtDesc(
//                        user, PageRequest.of(0, 20))); 直查最近20条。写死的，改成放到配置中
                        user, PageRequest.of(0, orderProps.getPageSize())));
        return "orderList";
    }

    // 一般 GET查询、POST新建、PUT替换(将新的资源覆盖原有资源)、PATCH局部属性更新
    // PUT : 资源存在则delete、insert；资源不存在直接insert
    // PATCH : update 指定属性
    @PatchMapping(path = "/{orderId}", consumes = "application/json")
    public Order patchOrder(@PathVariable("orderId") Long orderId, @RequestBody Order patch) {
        Order order = orderJpaRepository.findById(orderId).get();
        if (patch.getDeliveryState() != null)
            order.setDeliveryState(patch.getDeliveryState());
        return orderJpaRepository.save(order);
    }

    @DeleteMapping("/{orderId}")
    @ResponseStatus(code = HttpStatus.NO_CONTENT) // 返回当前资源(删除后)的状态
    public void deleteOrder(@PathVariable("orderId") Long orderId) {
        try {
            orderJpaRepository.deleteById(orderId);
        } catch (EmptyResultDataAccessException e) {
            // 没有数据，删和不删最终是一样，所以没特殊处理，都按删除成功算
        }
    }
}
